package converterOCL;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import parser.astgen.NodeFactory;
import parser.util.ReflectiveVisitor;
import parser.util.TypeCheck;
import parser.util.Utility;
import project.Project;
import uml2.associationclasses.AssociationClass;
import uml2.kernel.AConstrainedElementConstraintOfConstrainedElement;
import uml2.kernel.Constraint;
import uml2.kernel.Element;
import uml2.kernel.Operation;
import uml2.kernel.Parameter;
import uml2.kernel.ParameterDirectionKindEnum;
import uml2.kernel.Property;
import uml2.kernel.UmlClass;
import uml2.kernel.ValueSpecification;
import uml2.ocl2.AssociationClassCallExp;
import uml2.ocl2.BooleanLiteralExp;
import uml2.ocl2.CollectionItem;
import uml2.ocl2.CollectionLiteralExp;
import uml2.ocl2.CollectionLiteralPart;
import uml2.ocl2.CollectionRange;
import uml2.ocl2.EnumLiteralExp;
import uml2.ocl2.ExpressionInOcl;
import uml2.ocl2.IfExp;
import uml2.ocl2.IntegerLiteralExp;
import uml2.ocl2.IterateExp;
import uml2.ocl2.IteratorExp;
import uml2.ocl2.LetExp;
import uml2.ocl2.OclExpression;
import uml2.ocl2.OperationCallExp;
import uml2.ocl2.PropertyCallExp;
import uml2.ocl2.RealLiteralExp;
import uml2.ocl2.StringLiteralExp;
import uml2.ocl2.TupleLiteralExp;
import uml2.ocl2.TupleLiteralPart;
import uml2.ocl2.TypeExp;
import uml2.ocl2.Variable;
import uml2.ocl2.VariableExp;
import facade.UserFacade;
import facade.kernel.AConstrainedElementConstraintOfConstrainedElementFacade;

public class ConverterOCL extends ReflectiveVisitor{
	
	Project p;
	UserFacade uf;
	Utility ut;
	TypeCheck tc;
	
	LinkedList opwihtarg;

	boolean isIteratorExp = false;
	
	public ConverterOCL(Project p) {
		super("conversion");
		this.p = p;
		this.uf = new UserFacade(p);
		this.ut = new Utility(this.uf, p);
		
		this.tc = new TypeCheck(p,new NodeFactory(p));
		
		this.opwihtarg = new LinkedList();
		this.opwihtarg.add("oclAsType");
		this.opwihtarg.add("oclIsTypeOf");
		this.opwihtarg.add("oclIsKindOf");
	}

	// Method to use externally (Only this one is allowed to use)
	public LinkedList convertToOcl() {
		LinkedList stringresults = new LinkedList();
		
		// First, we have to obtain the all constraints in the repository
		UserFacade uf = new UserFacade(p);
		LinkedList constraints = (LinkedList) uf.getAllConstraints();
		
		Iterator it = constraints.iterator();
		while (it.hasNext()) {
			Constraint c = (Constraint) it.next();
			stringresults.add(convert(c));
		}
		return stringresults;
	}
	
	// First step of the visitor
	public String convert(Element oce) {
		String result = "";
		try {
			result = (String) visit(oce);
		} catch (NoSuchMethodException e) {
			//some  method is missing in the visitor implementation
			e.printStackTrace();
		} catch (java.lang.reflect.InvocationTargetException e){
			Throwable nestedException = e.getTargetException();
			nestedException.printStackTrace();
		}
		return result;
	}
	
	// Specific conversion methods for any element used
	
	public String conversion(Constraint c) {
		String name = c.getName();
		ValueSpecification spec = c.getSpecification();
		String context = "";
		String body = "";
		String kind = "";
		
		AConstrainedElementConstraintOfConstrainedElementFacade acecocef = new AConstrainedElementConstraintOfConstrainedElementFacade(p);
		AConstrainedElementConstraintOfConstrainedElement acecoce = acecocef.getAConstrainedElementConstraintOfConstrainedElement();
		List constrainedElement = acecoce.getConstrainedElement(c);
		Element ce = (Element) constrainedElement.get(0);
		
		if (spec instanceof ExpressionInOcl) {
			ExpressionInOcl eio = (ExpressionInOcl) spec;
			kind = converConstraintKind(eio);
			context = convertContext(ce, kind);
			body = convert(eio.getBodyExpression());
			
			if (kind.equals("def")) {
				String sclass = eio.getContextVariable().getType().getName();
				return "\ncontext "+sclass+"\n"+kind+" "+name+": "+context+" = "+body;
			}
			else {
				return "\ncontext "+context+"\n"+kind+" "+name+": "+body;
			}
		}
		else return spec.toString();
	}

	private String converConstraintKind(ExpressionInOcl eio) {
		// inv, def, init
		return eio.getSymbol(); 
	}

	public String conversion(OperationCallExp oce) {
		String source, op = "";
		
		source = convert(oce.getSource());
		op = convert(oce.getReferredOperation());
		
		if (tc.isCollectionOp(op)||tc.isAdditionalOp(op)) {
			String linker;
			if (tc.isCollectionOp(op)) linker = "->";
			else linker = ".";
			
			Collection params = oce.getArgument();
			if (params.isEmpty()) {
				return source+linker+op+"()";
			}
			else if (params.size()==1) {
				OclExpression e = (OclExpression) oce.getArgument().toArray()[0];
				String param = convert(e);
				return source+linker+op+"("+param+")";
			}
			else if (params.size()==2) {
				OclExpression e1 = (OclExpression) oce.getArgument().toArray()[0];
				OclExpression e2 = (OclExpression) oce.getArgument().toArray()[1];
				String param1 = convert(e1);
				String param2 = convert(e2);
				return source+linker+op+"("+param1+", "+param2+")";
			}
		}
		
		if (op.compareTo("allInstances")==0) {
			return source+".allInstances()";
		}
		
		if (op.compareTo("oclIsNew")==0) {
			return source+".oclIsNew()";
		}
		
		if (op.compareTo("@pre")==0) {
			String pars = "";
			if (source.endsWith(")")){
				int init = source.indexOf("(");
				pars = source.substring(init, source.length());
				source = source.substring(0, init);
			}
			return source+op+pars;
		}
		
		if (tc.isOtherBasicOp(op) || this.opwihtarg.contains(op)) {
		
			// Unary operation (-, not)
			if (oce.getArgument().size()==0){
				if (op.compareTo("-")==0 || op.compareTo("not")==0) {
					return op+" ( "+source+" ) ";
				}
				else return op+" "+source;
			}
			// Other bynary operations (source op argument) like +,-,*, ...
			else if (oce.getArgument().size()==1) {
				OclExpression e = (OclExpression) oce.getArgument().toArray()[0];
				String arg = convert(e);
			
				if (this.opwihtarg.contains(op)) {
					if (source.endsWith("."))
						return source+""+op+"("+arg+")";
					else if (source.equals("")) return op+"("+arg+")";
					else return source+"."+op+"("+arg+")";
				}
			return source+" "+op+" "+arg;
			}
		}
		
		// Operation of UML class
		String pars = "(";
		Collection params = oce.getArgument();
		if (params != null) {
			for (Iterator iterator = params.iterator(); iterator.hasNext();) {
				OclExpression arg = (OclExpression) iterator.next();
				String parexp = convert(arg);
				pars = pars+parexp+",";
			}
			if (pars.endsWith(",")) pars = pars.substring(0, pars.length()-1) + ")";
		}
		if (!pars.endsWith(")")) pars = pars+")";
		return source+"."+op+pars;
		
	}
	
	public String conversion(TypeExp te) {
		return te.getReferredType().getName();
	}
	
	public String conversion(IntegerLiteralExp ile) {
		return Integer.toString(ile.getIntegerSymbol());
	}
	
	public String conversion(RealLiteralExp rle) {
		return Float.toString(rle.getRealSymbol());
	}
	
	public String conversion(BooleanLiteralExp ble) {
		return Boolean.toString(ble.isBooleanSymbol());
		//return ble.getName();
	}
	
	public String conversion(StringLiteralExp sle) {
		return "'"+sle.getStringSymbol()+"'";
	}
	
	public String conversion(EnumLiteralExp ele) {
		return ele.getType().getName()+"::"+ele.getReferredEnumLiteral().getName();
	}
	
	public String conversion(Operation op) {
		return op.getName();
	}

	public String conversion(PropertyCallExp pce) {
		String source = convert(pce.getSource());
		if (!source.equals("") && !source.endsWith(".")) source = source.concat(".");
		
		Property pr = pce.getReferredProperty();
		if (ut.propertyIsAssociationEnd(pce.getReferredProperty())) {
			if (pr.getName().equals("")) { // is a association end without name
				return source+ut.convertToLower(pr.getType().getName());
			}
		}
		return source+pr.getName();
	}
	
	public String conversion(AssociationClassCallExp acce) {
		String source = convert(acce.getSource());
		AssociationClass ac = acce.getReferredAssociationClass();
		// code for convert Classname to classname (lower case)
		String acname = ut.convertToLower(ac.getName());
		Collection qualifiers = acce.getQualifier();
		if (!qualifiers.isEmpty()) {
			// only one qualifier is able to exist
			String quname = ((OclExpression)qualifiers.toArray()[0]).getName();
			return source+"."+acname+"["+quname+"]";
		} else {
			return source+"."+acname;
		}
	}
	
	public String conversion(IfExp ie) {
		String cond = convert(ie.getCondition());
		String thenbranch = convert(ie.getThenExpression());
		String elsebranch = convert(ie.getElseExpression());
		String result = "if "+cond+"\n    then "+thenbranch+"\n    else "+elsebranch;
		return result;
	}
	
	public String conversion(IteratorExp ie) {
		String source = convert(ie.getSource());
		String itname = ie.getName();
		
		this.isIteratorExp = true;
		String body = convert(ie.getBody());
		this.isIteratorExp = false;
		
		Collection vars = ie.getIterator();
		
		if (vars.size()==1 && ((Variable)vars.iterator().next()).getName().equals("")) return source+"->"+itname+"("+body+")";
		else {
			String iterators;
			Iterator i = vars.iterator();
			Variable v = (Variable) i.next();	
			iterators = convert(v);
			while (i.hasNext()) {
				v = (Variable) i.next();
				iterators+=","+convert(v);
			}
			if (uf.findClassByName(v.getType().getName())!=null) {
				iterators+=":"+v.getType().getName();
			}
				
			iterators += " | ";
			return source+"->"+itname+"("+iterators+""+body+")";
		}
	}
	
	public String conversion(IterateExp ie) {
		String source = convert(ie.getSource());
		String itname = ie.getName();
		
		this.isIteratorExp = true;
		String body = convert(ie.getBody());
		this.isIteratorExp = false;
		
		String result = convertInitializedVariable(ie.getResult());
		
		Collection vars = ie.getIterator();
		
		if (vars.size()==1 && ((Variable)vars.iterator().next()).getName().equals("")){
			return source+"->"+itname+"( ; "+result+ " | "+body+")";
		}
		else {
			String iterators;
			Iterator i = vars.iterator();
			Variable v = (Variable) i.next();	
			iterators = convert(v);
			while (i.hasNext()) {
				v = (Variable) i.next();
				iterators+=","+convert(v);
			}
			if (uf.findClassByName(v.getType().getName())!=null) {
				iterators+=":"+v.getType().getName();
			}
				
			return source+"->"+itname+"("+iterators+"; "+result+ " | "+body+")";
		}
	}
	
	public String conversion(LetExp le) {
		String var = convertInitializedVariable(le.getVariable());
		String in = convert(le.getIn());
		
		return "let "+var+"\nin\n"+in;
	}
	
	private String convertInitializedVariable(Variable initvar) {
		String init = convert(initvar.getInitExpression());
		String type = initvar.getType().getName();
		String name = initvar.getName();
		return name+":"+type+" = "+init;
	}

	public String conversion(Variable v) {
		return v.getName();
	}
	
	public String conversion(VariableExp ve) {
		Variable v = ve.getReferredVariable();
		
		if (v.getName().equals("contextual_classifier")) {
			return v.getType().getName(); 
		}
		else return v.getName();
	}
	
	public String conversion(CollectionLiteralExp cle) {
		String collectionkind = cle.getKind().toString();
		String result = collectionkind+"(";
		Collection part = cle.getPart();
		for (Iterator iterator = part.iterator(); iterator.hasNext();) {
			CollectionLiteralPart clp = (CollectionLiteralPart) iterator.next();
			result += convert(clp)+",";
		}
		result = result.substring(0, result.length()-1);
		result += ")";
		return result;
	}
 
	public String conversion(CollectionRange cr) {
		String first = convert(cr.getFirst());
		String last = convert(cr.getLast());
		return first+".."+last;
	}
	
	public String conversion(CollectionItem ci) {
		return convert(ci.getItem());
	}
	
	public String conversion(TupleLiteralExp tle) {
		String result = "Tuple{";
		Collection tlparts = tle.getPart();
		for (Iterator iterator = tlparts.iterator(); iterator.hasNext();) {
			TupleLiteralPart tlp = (TupleLiteralPart) iterator.next();
			String value = convert(tlp.getValue());
			String type = tlp.getAttribute().getType().getName();
			String name = tlp.getAttribute().getName();
			result = result + name+":"+type+" = "+value+",";
		}
		if (result.endsWith(",")) {
			result = result.substring(0, result.length()-1);
		}
		result = result+"}";
		return result;
	}
	
	private String convertContext(Element context, String kind) {
		if (kind.equals("inv")) {
			UmlClass ctx = (UmlClass) context;
			return ctx.getName();
		}
		else if (kind.equals("init") || kind.equals("derive")) {
			Property p = (Property) context;
			return p.getUmlclass().getName()+"::"+p.getName()+" : "+p.getType().getName();
		}
		else if (kind.equals("def")) {
			String returntype = "";
			if (context instanceof Operation) {
				Operation op = (Operation) context;
				String result = op.getUmlclass().getName()+"::"+op.getName()+"(";
				List pars = op.getOwnedParameter();
				for (Iterator iterator = pars.iterator(); iterator.hasNext();) {
					Parameter param = (Parameter) iterator.next();
					if (param.getDirection() == ParameterDirectionKindEnum.RETURN) {
						returntype = " : "+param.getType().getName();
					}
					else {
						result = result+param.getName()+":"+param.getType().getName()+",";
					}
				}
				if (result.endsWith(",")) result = result.substring(0, result.length()-1);
				result = result + ")"+returntype;
				return result;
			}
			else if (context instanceof Property) {
				Property p = (Property) context;
				return p.getName()+" : "+p.getType().getName();
			}
			else return "ERROR(def)";
		}
		else if (kind.equals("pre") || kind.equals("body") || kind.equals("post")) {
			String returntype = "";
			Operation op = (Operation) context;
			String result = op.getUmlclass().getName()+"::"+op.getName()+"(";
			List pars = op.getOwnedParameter();
			for (Iterator iterator = pars.iterator(); iterator.hasNext();) {
				Parameter param = (Parameter) iterator.next();
				if (param.getDirection() == ParameterDirectionKindEnum.RETURN) {
					returntype = " : "+param.getType().getName();
				}
				else {
					result = result+param.getName()+":"+param.getType().getName()+",";
				}
			}
			if (result.endsWith(",")) result = result.substring(0, result.length()-1);
			result = result + ")"+returntype;
			return result;
		}
		else return "ERROR";
	}
}



